﻿/**
* CRL
*/
using Elasticsearch.Net;
using CRL.Core;
using CRL;
using CRL.LambdaQuery;
using Nest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using innerAttr = CRL.Attribute;
namespace CRL.Elasticsearch.ESEx
{
    /// <summary>
    /// ES不支持关联和直接语句查询
    /// 部份扩展方法支持
    /// </summary>
    public sealed partial class ESExtend
    {
        List<Dictionary<string, long>> GetAggregationCount<TModel>(ESLambdaQuery<TModel> query, out ISearchResponse<TModel> result) where TModel : class
        {
            var sr = getSearchRequest(query);
            result = getClient().Search<TModel>(sr);
            var groupDic = new List<Dictionary<string, long>>();
            foreach (var f in query.__GroupFields)
            {
                var field = f.FieldName;
                var group1 = result.Aggregations.Terms($"{field}Count");
                if (group1 == null)
                {
                    groupDic.Add(new Dictionary<string, long>());
                    continue;
                }
                var dict = group1.Buckets.ToDictionary(b => b.Key, b => b.DocCount ?? 0);
                groupDic.Add(dict);
            }
            return groupDic;
        }
        List<dynamic> GetDynamicResult<TModel>(ILambdaQuery<TModel> iQuery) where TModel : class
        {
            var query = iQuery as ESLambdaQuery<TModel>;
            var selectField = query.GetFieldMapping();
            if (query.__GroupFields != null)
            {
                var groupDic = GetAggregationCount(query, out var result);
                return groupDic.Select(b => b as dynamic).ToList();
                throw new Exception("暂不支持group");
            }
            else if (query.__DistinctFields)
            {
                throw new Exception("暂不支持distinct");
            }
            else
            {
                #region 动态类型
                var sr = getSearchRequest(query);
                var result = getClient().Search<TModel>(sr).Documents.ToList();
                var list = new List<dynamic>();
                var fields = TypeCache.GetTable(typeof(TModel)).FieldsDic;
                foreach (var item in result)
                {
                    dynamic obj = new System.Dynamic.ExpandoObject();
                    var dict = obj as IDictionary<string, object>;
                    foreach (var f in selectField)
                    {
                        string columnName = f.ResultName;
                        object value = fields[columnName].GetValue(item);
                        dict.Add(columnName, value);
                    }
                    list.Add(obj);
                }
                #endregion
                return list;
            }
        }
        #region QueryDynamic
        public override List<dynamic> QueryDynamic(LambdaQueryBase query)
        {
            throw new NotSupportedException("ES暂未实现此方法");
        }
        public List<dynamic> QueryDynamic2<TModel>(LambdaQuery<TModel> query) where TModel : class
        {
            var result = GetDynamicResult(query);
            return result;
        }
        #endregion

        #region QueryResult

        /// <summary>
        /// 按select返回指定类型
        /// </summary>
        /// <typeparam name="TResult"></typeparam>
        /// <param name="query"></param>
        /// <returns></returns>
        List<TResult> QueryResult<TResult>(LambdaQueryBase query)
        {
            var typeDb = this.GetType();
            var method = typeDb.GetMethod(nameof(QueryResultByType), BindingFlags.NonPublic | BindingFlags.Instance);
            var result = method.MakeGenericMethod(new Type[] { query.__MainType, typeof(TResult) }).Invoke(this, new object[] { query });
            return result as List<TResult>;

        }

        public override List<TResult> QueryResult<TResult>(LambdaQueryBase query, NewExpression newExpression = null)
        {
            if (newExpression == null)
            {
                return QueryResult<TResult>(query);
            }
            var typeDb = this.GetType();
            var method = typeDb.GetMethod(nameof(QueryResultNewExpression), BindingFlags.NonPublic | BindingFlags.Instance);
            var result = method.MakeGenericMethod(new Type[] { query.__MainType, typeof(TResult) }).Invoke(this, new object[] { query, newExpression });
            return result as List<TResult>;
        }

        List<TResult> QueryResultByType<TModel, TResult>(LambdaQuery<TModel> query) where TModel : class
        {
            var result = GetDynamicResult(query);
            var type = typeof(TResult);
            var pro = type.GetProperties();
            var list = new List<TResult>();
            var reflection = ReflectionHelper.GetInfo<TResult>();
            foreach (var item in result)
            {
                var dict = item as IDictionary<string, object>;
                var obj = (TResult)System.Activator.CreateInstance(type);
                foreach (var f in pro)
                {
                    string columnName = f.Name;
                    if (dict.ContainsKey(columnName))
                    {
                        object value = dict[columnName];
                        var access = reflection.GetAccessor(columnName);
                        access.Set((TResult)obj, value);
                    }
                }
                list.Add(obj);
            }
            return list;
        }


        static Func<ObjContainer, T> CreateObjectGenerator<T>(ConstructorInfo constructor)
        {
            var type = typeof(ObjContainer);
            var parame = Expression.Parameter(type, "par");
            var parameters = constructor.GetParameters();
            List<Expression> arguments = new List<Expression>(parameters.Length);
            foreach (var parameter in parameters)
            {
                var method = ObjContainer.GetMethod(parameter.ParameterType, true);
                var getValue = Expression.Call(parame, method, Expression.Constant(parameter.Name));
                arguments.Add(getValue);
            }
            var body = Expression.New(constructor, arguments);
            var ret = Expression.Lambda<Func<ObjContainer, T>>(body, parame).Compile();
            return ret;
        }


        static Func<ObjContainer, T> CreateObjectGeneratorFromMapping<T>(IEnumerable<innerAttr.FieldMapping> mapping)
        {
            var objectType = typeof(T);
            var fields = TypeCache.GetProperties(objectType, true);
            var parame = Expression.Parameter(typeof(ObjContainer), "par");
            var memberBindings = new List<MemberBinding>();
            //按顺序生成Binding
            //int i = 0;
            foreach (var mp in mapping)
            {
                if (!fields.ContainsKey(mp.ResultName))
                {
                    continue;
                }
                var m = fields[mp.ResultName].GetPropertyInfo();
                var method = ObjContainer.GetMethod(m.PropertyType, true);
                //Expression getValue = Expression.Call(method, parame);
                var getValue = parame.Call(method.Name, Expression.Constant(mp.ResultName));
                if (m.PropertyType.IsEnum)
                {
                    getValue = Expression.Convert(getValue, m.PropertyType);
                }
                var bind = (MemberBinding)Expression.Bind(m, getValue);
                memberBindings.Add(bind);
                //i += 1;
            }
            Expression expr = Expression.MemberInit(Expression.New(objectType), memberBindings);
            var ret = Expression.Lambda<Func<ObjContainer, T>>(expr, parame);
            return ret.Compile();
        }


        List<TResult> QueryResultNewExpression<TModel, TResult>(LambdaQuery<TModel> query, NewExpression newExpression) where TModel : class
        {
            //query.Select(newExpression);
            var result = GetDynamicResult(query);
            var list = new List<TResult>();
            Func<ObjContainer, TResult> objCreater;
            var parameters = newExpression.Constructor.GetParameters();
            //当匿名类型指定了类型,没有构造参数
            if (parameters.Length > 0)
            {
                objCreater = CreateObjectGenerator<TResult>(newExpression.Constructor);
            }
            else
            {
                objCreater = CreateObjectGeneratorFromMapping<TResult>(query.GetFieldMapping());
            }

            foreach (IDictionary<string, object> item in result)
            {
                var objC = new ObjContainer(item);
                var obj = objCreater(objC);
                list.Add(obj);
            }
            return list;
        }
        #endregion

        internal SearchRequest getSearchRequest<TModel>(ESLambdaQuery<TModel> query)
        {
            var sr = new SearchRequest(IndexName);
            sr.Query = query.queryContainers.Count > 1 ? new BoolQuery { Filter = query.queryContainers } : query.queryContainers.FirstOrDefault();
            sr.Sort = query._ESSort;
            //var dicLog = QueryContainerVister.vister(sr.Query,new Dictionary<string, object>());
            //var json = SerializeHelper.SerializerToJson(dicLog);
            var page = query.SkipPage;
            var pageSize = query.TakeNum;
            if (pageSize == 0)
            {
                pageSize = 50000;
            }
            if (page == 0)
            {
                page = 1;
            }
            sr.From = (page - 1) * pageSize;
            sr.Size = pageSize;
            sr.TrackTotalHits = true;
            var selectField = query.GetFieldMapping();
            Fields fields = selectField.Select(b => b.FieldName).ToArray();
            sr.Source = new SourceFilter
            {
                Includes = fields,
            };
            if (query.__GroupFields != null)
            {
                var aggrDic = new AggregationDictionary();
                foreach (var f in query.__GroupFields)
                {
                    var field = f.FieldName;
                    aggrDic.Add($"{field}Count", new TermsAggregation($"{field}Count")
                    {
                        Field = new Field(field + ".keyword"),
                    });
                }
                sr.Aggregations = aggrDic;
            }

            return sr;
        }
        public override List<TModel> QueryOrFromCache<TModel>(ILambdaQuery<TModel> query, out string cacheKey)
        {
            var method = GetType().GetMethod(nameof(QueryData), BindingFlags.Instance | BindingFlags.NonPublic);
            var result = method.MakeGenericMethod(new Type[] { typeof(TModel) }).Invoke(this, new object[] { query }) as List<TModel>;
            cacheKey = "";
            return result;
        }
        internal string getQueryJson(SearchRequest sr)
        {
            return getClient().RequestResponseSerializer.SerializeToString(sr);
        }
        List<TModel> QueryData<TModel>(ILambdaQuery<TModel> iQuery) where TModel : class
        {
            //cacheKey = "none";
            var query = iQuery as ESLambdaQuery<TModel>;
            ISearchResponse<TModel> result;
            query._AggregationCount = null;
            if (query.__GroupFields != null)
            {
                query._AggregationCount = GetAggregationCount(query, out result);
            }
            else
            {
                var sr = getSearchRequest(query);
                result = getClient().Search<TModel>(sr);
            }
            Log("Search", result);
            query.__RowCount = (int)result.Total;
            var scores = result.Hits.ToDictionary(b => b.Id, b => b.Score);
            //SetOriginClone(result);
            return result.Documents.ToList();
        }
        void Log(string name, IResponse result)
        {
            if (!result.IsValid)
            {
                var msg = $"{name} {result.ServerError} {result.OriginalException}";
                Console.WriteLine(msg);
                throw new Exception(msg);
            }
        }
        public override Dictionary<TKey, TValue> ToDictionary<TModel, TKey, TValue>(ILambdaQuery<TModel> query)
        {
            var dic = new Dictionary<TKey, TValue>();
            var method = GetType().GetMethod(nameof(GetDynamicResult), BindingFlags.Instance | BindingFlags.NonPublic);
            var result = method.MakeGenericMethod(new Type[] { typeof(TModel) }).Invoke(this, new object[] { query }) as List<dynamic>;
            if (result.Count == 0)
            {
                return dic;
            }
            var first = result.First() as IDictionary<string, object>;
            var keys = first.Keys.ToList();
            var keyName = keys[0];
            var valueName = keys[1];
            foreach (var item in result)
            {
                var obj = item as IDictionary<string, object>;
                dic.Add((TKey)obj[keyName], (TValue)obj[valueName]);
            }
            return dic;
        }
        public override dynamic QueryScalar<TModel>(ILambdaQuery<TModel> query)
        {
            var method = GetType().GetMethod(nameof(GetDynamicResult), BindingFlags.Instance | BindingFlags.NonPublic);
            var result = method.MakeGenericMethod(new Type[] { typeof(TModel) }).Invoke(this, new object[] { query }) as List<dynamic>;
            //var result = GetDynamicResult(query);
            if (result.Count == 0)
            {
                return null;
            }
            var first = result.First() as IDictionary<string, object>;
            var keys = first.Keys.ToList();
            return first[keys.First()];
        }
        public IReadOnlyCollection<AnalyzeToken> Analyze(string txt, string analyser)
        {
            var client = getClient();
            var words = client.Indices.Analyze(a => a.Analyzer(analyser).Text(txt));
            return words.Tokens;
        }
    }
}
